Skip to content

fix: revoke access token on duplicate auth code user#786

Merged
steveiliop56 merged 3 commits intomainfrom
fix/oidc-coderuse
Apr 14, 2026
Merged

fix: revoke access token on duplicate auth code user#786
steveiliop56 merged 3 commits intomainfrom
fix/oidc-coderuse

Conversation

@steveiliop56
Copy link
Copy Markdown
Member

@steveiliop56 steveiliop56 commented Apr 11, 2026

image

Summary by CodeRabbit

  • New Features

    • Access tokens are now invalidated if an authorization code is reused; reusing a code returns an "invalid_grant" error and previously issued tokens become unusable (subsequent API calls fail).
  • Tests

    • Added test coverage confirming tokens are invalidated on double use and that appropriate errors are returned.

@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Apr 11, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 11, 2026

📝 Walkthrough

Walkthrough

Adds tracking of authorization code hashes to persisted OIDC tokens and invalidates tokens tied to a code hash when a code exchange fails (e.g., reused code). Controller now attempts to delete tokens by code hash on code lookup errors before returning the token exchange error response.

Changes

Cohort / File(s) Summary
Database schema & migrations
sql/oidc_schemas.sql, internal/assets/migrations/000008_oidc_code_reuse.up.sql, internal/assets/migrations/000008_oidc_code_reuse.down.sql
Add non-null code_hash TEXT NOT NULL column to oidc_tokens and provide up/down migrations.
SQL queries
sql/oidc_queries.sql, internal/repository/oidc_queries.sql.go
Persist code_hash on token creation, include code_hash in token-returning queries, and add DeleteOidcTokenByCodeHash exec query.
Repository models
internal/repository/models.go
Add CodeHash string to OidcToken model.
Service layer
internal/service/oidc_service.go
Propagate codeEntry.CodeHash into created tokens; add DeleteTokenByCodeHash service method delegating to repository.
Controller & tests
internal/controller/oidc_controller.go, internal/controller/oidc_controller_test.go
Controller token handler calls service.DeleteTokenByCodeHash on code lookup errors; tests updated/added to verify code reuse invalidates previously issued access tokens and returns invalid_grant on reuse.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller as OIDC Controller
    participant Service as OIDC Service
    participant Repo as Token Repository
    participant DB as Database

    Client->>Controller: POST /token (authorization_code)
    Controller->>Repo: GetCodeEntry(codeHash)
    alt code lookup error (not found/expired/invalid_client)
        Repo-->>Controller: error
        Controller->>Service: DeleteTokenByCodeHash(codeHash)
        Service->>Repo: DeleteOidcTokenByCodeHash(codeHash)
        Repo->>DB: DELETE FROM oidc_tokens WHERE code_hash = ?
        DB-->>Repo: result
        Repo-->>Service: OK/error
        Service-->>Controller: OK/error
        Controller-->>Client: HTTP 400/401 (appropriate token error)
    else code valid
        Repo-->>Controller: codeEntry
        Controller->>Service: CreateOidcToken(..., codeHash)
        Service->>Repo: CreateOidcToken(...)
        Repo->>DB: INSERT INTO oidc_tokens (..., code_hash, ...)
        DB-->>Repo: inserted row
        Repo-->>Service: created token
        Service-->>Controller: token
        Controller-->>Client: HTTP 200 (access_token)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A reused code hopped twice one day,
I tracked its hash and tucked tokens away.
With nibble and thump I cleaned the trail,
So stale tokens fail and security prevails. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title directly describes the main change: adding access token revocation when an authorization code is reused, which aligns with the changeset's core functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/oidc-coderuse

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 11, 2026

Codecov Report

❌ Patch coverage is 0% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 20.09%. Comparing base (cc94294) to head (51a6559).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
internal/repository/oidc_queries.sql.go 0.00% 10 Missing ⚠️
internal/service/oidc_service.go 0.00% 3 Missing ⚠️
internal/controller/oidc_controller.go 0.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #786      +/-   ##
==========================================
- Coverage   20.12%   20.09%   -0.04%     
==========================================
  Files          50       50              
  Lines        3970     3987      +17     
==========================================
+ Hits          799      801       +2     
- Misses       3099     3113      +14     
- Partials       72       73       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
sql/oidc_schemas.sql (1)

16-16: Consider indexing oidc_tokens.code_hash for the new revoke path.

DeleteOidcTokenByCodeHash now uses this column directly; an index avoids full-table scans as token volume grows.

Suggested schema addition
 CREATE TABLE IF NOT EXISTS "oidc_tokens" (
@@
     "nonce" TEXT DEFAULT ""
 );
+
+CREATE INDEX IF NOT EXISTS "idx_oidc_tokens_code_hash" ON "oidc_tokens" ("code_hash");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@sql/oidc_schemas.sql` at line 16, Add a non-unique index on the
oidc_tokens.code_hash column to avoid full-table scans when
DeleteOidcTokenByCodeHash looks up tokens by code_hash; implement this as a
migration that creates the index (use CREATE INDEX CONCURRENTLY IF NOT EXISTS on
oidc_tokens(code_hash) so it won’t lock the table) and ensure the migration is
applied before deploying the revoke path change.
internal/repository/oidc_queries.sql.go (1)

276-283: Add index or uniqueness constraint on oidc_tokens(code_hash).

DeleteOidcTokenByCodeHash will scan the entire oidc_tokens table without an index on code_hash. Without a UNIQUE constraint, multiple rows could also be deleted if duplicates exist. Consider adding at least an index, ideally a UNIQUE constraint on this column to match the oidc_codes.code_hash pattern.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/repository/oidc_queries.sql.go` around lines 276 - 283, The
DeleteOidcTokenByCodeHash query (deleteOidcTokenByCodeHash /
Queries.DeleteOidcTokenByCodeHash) will do a full table scan and may remove
multiple rows if duplicates exist; add a database migration to create an index
on oidc_tokens(code_hash) and make it UNIQUE (or at minimum a non-unique index)
to match the pattern used for oidc_codes.code_hash, run the migration so the
schema is updated, and then keep the existing query; if business logic allows
multiple tokens per code_hash, document that and leave a non-unique index
instead of UNIQUE.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/assets/migrations/000008_oidc_coder_user.up.sql`:
- Line 1: The migration adds a nullable "code_hash" column but the schema
expects NOT NULL; update the ALTER TABLE in 000008_oidc_coder_user.up.sql to
create the column with a non-null constraint and a default empty string (e.g.,
ALTER TABLE "oidc_tokens" ADD COLUMN "code_hash" TEXT NOT NULL DEFAULT '';),
ensuring the "oidc_tokens" table and "code_hash" column match
sql/oidc_schemas.sql and migrated databases behave the same as fresh installs.

In `@internal/controller/oidc_controller.go`:
- Around line 278-280: The call to controller.oidc.DeleteTokenByCodeHash(c,
controller.oidc.Hash(req.Code)) is currently ignored; change it to capture the
returned error, check it, and handle failures instead of proceeding silently.
Specifically, assign the result (err :=
controller.oidc.DeleteTokenByCodeHash(...)), and if err != nil: log the error
with context (including the code hash and request info) and return an
appropriate HTTP error response from the handler (or bail out) so the replay
path doesn’t continue with a possibly-valid token; treat a benign "not found"
case differently only if your token store exposes that sentinel error. Ensure
you reference controller.oidc.DeleteTokenByCodeHash,
controller.oidc.Hash(req.Code), the request object req.Code and the request
context c when implementing this.

In `@internal/repository/oidc_queries.sql.go`:
- Line 226: Migration 000008 (and any other migrations that add the "code_hash"
column) currently adds code_hash with DEFAULT "" but no backfill or NOT NULL,
causing runtime scan panics when reading into the non-nullable Go field
i.CodeHash; update the migration SQL that adds "code_hash" to include an
explicit UPDATE to set "" for existing NULLs and then ALTER COLUMN ... SET NOT
NULL (i.e. ADD COLUMN "code_hash" TEXT DEFAULT ""; UPDATE "oidc_tokens" SET
"code_hash" = "" WHERE "code_hash" IS NULL; ALTER TABLE "oidc_tokens" ALTER
COLUMN "code_hash" SET NOT NULL;), and apply the same change to any other
migration snippets that later scan code_hash (references: oidc_queries.sql.go
scanning into &i.CodeHash).

---

Nitpick comments:
In `@internal/repository/oidc_queries.sql.go`:
- Around line 276-283: The DeleteOidcTokenByCodeHash query
(deleteOidcTokenByCodeHash / Queries.DeleteOidcTokenByCodeHash) will do a full
table scan and may remove multiple rows if duplicates exist; add a database
migration to create an index on oidc_tokens(code_hash) and make it UNIQUE (or at
minimum a non-unique index) to match the pattern used for oidc_codes.code_hash,
run the migration so the schema is updated, and then keep the existing query; if
business logic allows multiple tokens per code_hash, document that and leave a
non-unique index instead of UNIQUE.

In `@sql/oidc_schemas.sql`:
- Line 16: Add a non-unique index on the oidc_tokens.code_hash column to avoid
full-table scans when DeleteOidcTokenByCodeHash looks up tokens by code_hash;
implement this as a migration that creates the index (use CREATE INDEX
CONCURRENTLY IF NOT EXISTS on oidc_tokens(code_hash) so it won’t lock the table)
and ensure the migration is applied before deploying the revoke path change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: aed6d574-234d-4920-8a02-07e4e77c7c56

📥 Commits

Reviewing files that changed from the base of the PR and between cc94294 and 8622736.

📒 Files selected for processing (9)
  • internal/assets/migrations/000008_oidc_coder_user.down.sql
  • internal/assets/migrations/000008_oidc_coder_user.up.sql
  • internal/controller/oidc_controller.go
  • internal/controller/oidc_controller_test.go
  • internal/repository/models.go
  • internal/repository/oidc_queries.sql.go
  • internal/service/oidc_service.go
  • sql/oidc_queries.sql
  • sql/oidc_schemas.sql

Comment thread internal/assets/migrations/000008_oidc_coder_user.up.sql Outdated
Comment thread internal/controller/oidc_controller.go Outdated
Comment thread internal/repository/oidc_queries.sql.go
Comment thread internal/assets/migrations/000008_oidc_coder_user.up.sql Outdated
@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Apr 14, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
internal/controller/oidc_controller.go (1)

278-280: ⚠️ Potential issue | 🟠 Major

Don’t continue when replay-token revocation fails.

Line 278 logs DeleteTokenByCodeHash failures but still proceeds with the original error response. That can leave a replay-associated token active. Please fail closed (return server_error) when revocation fails.

Suggested fix
-			if err := controller.oidc.DeleteTokenByCodeHash(c, controller.oidc.Hash(req.Code)); err != nil {
-				tlog.App.Error().Err(err).Msg("Failed to delete access token by code hash")
-			}
+			codeHash := controller.oidc.Hash(req.Code)
+			if delErr := controller.oidc.DeleteTokenByCodeHash(c, codeHash); delErr != nil {
+				tlog.App.Error().Err(delErr).Str("code_hash", codeHash).Msg("Failed to delete access token by code hash")
+				c.JSON(400, gin.H{
+					"error": "server_error",
+				})
+				return
+			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controller/oidc_controller.go` around lines 278 - 280, The current
code logs failures from controller.oidc.DeleteTokenByCodeHash(c,
controller.oidc.Hash(req.Code)) but continues processing, which can leave a
replayable token active; change the handler so that if DeleteTokenByCodeHash
returns an error you log it (using tlog.App.Error().Err(err).Msg(...)) and then
immediately return a server_error response (i.e., fail closed) instead of
proceeding with the original error flow; update the code path around
DeleteTokenByCodeHash to propagate/return the server_error on revocation
failure.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@internal/controller/oidc_controller.go`:
- Around line 278-280: The current code logs failures from
controller.oidc.DeleteTokenByCodeHash(c, controller.oidc.Hash(req.Code)) but
continues processing, which can leave a replayable token active; change the
handler so that if DeleteTokenByCodeHash returns an error you log it (using
tlog.App.Error().Err(err).Msg(...)) and then immediately return a server_error
response (i.e., fail closed) instead of proceeding with the original error flow;
update the code path around DeleteTokenByCodeHash to propagate/return the
server_error on revocation failure.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1ba7124a-d4da-40b7-8dc6-6138c9087627

📥 Commits

Reviewing files that changed from the base of the PR and between 5b836ee and 51a6559.

📒 Files selected for processing (2)
  • internal/controller/oidc_controller.go
  • internal/controller/oidc_controller_test.go

@steveiliop56 steveiliop56 merged commit 6f99e7a into main Apr 14, 2026
8 checks passed
@Rycochet Rycochet deleted the fix/oidc-coderuse branch April 26, 2026 15:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants